package com.qfx.modules.common.aspect;

import java.util.Arrays;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Aspect		// 将⼀个java类定义为切⾯类
@Component
public class WebLogAspect {

	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	
	ThreadLocal<Long> startTime = new ThreadLocal<>();
	
	/**
	 * <h5>功能:定义一个切入点,可以是一个规则表达式,也可以是一个注解</h5>
	 * 这里匹配com.qfx.modules.*.controller包下所有类的所有方法
	*/
	@Pointcut("execution(* com.qfx.modules.*.controller..*(..))")
	public void webLog() {
	}
	
//	根据需要在不同位置切入目标方法:
//	@Before	前置通知(在目标方法执行之前执行)
//	@After  后置通知(在目标方法执行之后执行,无论目标方法执行成功还是出现异常,都将执行后置方法)
//	@AfterReturning 返回通知(在目标方法执行成功后才会执行,如果目标方法出现异常,则不执行;可以用来对返回值做一些加工处理)
//	@AfterThrowing	异常通知(只在目标方法出现异常后才会执行,否则不执行;可以获取目标方法出现的异常信息)
//	@Around	环绕通知(包含上面四种通知方法,环绕通知的功能最全面.环绕通知需要携带ProceedingJoinPoint类型的参数,且环绕通知必须有返回值,返回值即为目标方法的返回值)

	/**
	 * <h5>功能:前置通知</h5>
	 * 
	 * @param joinPoint
	*/
	@Before("webLog()")
	public void doBefore(JoinPoint joinPoint) {
		startTime.set(System.currentTimeMillis());
		
		// 接收到请求，记录请求内容
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = attributes.getRequest();
		// 记录下请求内容
		logger.info("-------------------- 前置通知处理开始 --------------------");
		logger.info("请求IP :{}", request.getRemoteAddr());
		logger.info("请求信息:[{}] {}", request.getMethod(), request.getRequestURL().toString());
		logger.info("调用方法:{} {}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
		logger.info("接收参数:{}", Arrays.toString(joinPoint.getArgs()));
		logger.info("-------------------- 前置通知处理结束 --------------------");
	}

	/**
	 * <h5>功能:后置通知</h5>
	 * 
	 * @param returnInfo 特殊参数(returning):设定使用通知方法参数接收返回值的变量名
	*/
	@AfterReturning(pointcut = "webLog()", returning = "returnInfo")
	public void doAfterReturning(Object returnInfo) {
		logger.info("-------------------- 后置通知处理开始 --------------------");
		// 处理完请求，返回内容
		logger.info("返回信息:{}", returnInfo);
		
		logger.info("耗时:{}秒", (System.currentTimeMillis() - startTime.get())/1000d);
		logger.info("-------------------- 后置通知处理结束 --------------------");
	}
	
	/**
	 * <h5>功能:异常通知(一般不使用这个)</h5>
	 * 通过throwing属性指定目标方法出现的异常信息存储在e变量中,在异常通知方法中就可以从e变量中获取异常信息了
	 * @param point
	 * @param e 特殊参数(throwing):设定使用通知方法参数接收原始方法中抛出的异常对象名
	*/
	@AfterThrowing(pointcut = "webLog()", throwing = "e")
	public void afterReturning(JoinPoint joinPoint, Exception e) {
		logger.error("-------------------- 异常通知处理开始 --------------------");
		logger.error("调用方法:{} {}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
		logger.error("接收参数:{}", Arrays.toString(joinPoint.getArgs()));
		logger.error("异常信息:", e);
		logger.error("-------------------- 异常通知处理结束 --------------------");
	}
}
